/*
MIT License 

Copyright (c) 2021 МГТУ им. Н.Э. Баумана, кафедра ИУ-6, Михаил Фетисов, 

https://bmstu.codes/lsx/simodo
*/

#ifndef simodo_token_Tokenizer
#define simodo_token_Tokenizer

/*! \file Tokenizer.h
    \brief Лексический анализатор

    Заголовочный файл, описывающий классы и структуры перевода текста входного потока в высокоуровневый алфавит (лексемы).
*/

#include "simodo/inout/token/Scanner.h"
#include "simodo/inout/token/LexicalParameters.h"

#include <vector>
#include <string>
#include <istream>


namespace simodo::inout
{
    /*!
     * \brief Класс лексического анализатора
     *
     * Данный лексический анализ имеет ряд важных особенностей:
     * 1. При подсвечивании синтаксиса в среде Qt 5 нужно учитывать контекст многострочных конструкций
     * таких, как многострочные комментарии и аннотации (строки в двойных кавычках).
     * Поэтому, предусмотрена работа с отложенным контекстом (см. enum class TokenContext)
     * 2. Комментарии (однострочные и многострочные) определяются и возвращаются как токены
     * 3. Идентификаторы (тип TokenType::Word) не могут иметь символы разных алфавитов.
     * Если такое замечено, то токен возвращается с уточнением TokenQualification::NationalCharMix
     *
     * В остальном работа лексического анализатора стандартна.
     *
     * При разборе (работа Parser и т.п.) после создания объекта Tokenizer выполняются вызовы getToken,
     * пока возвращённый токен не будет типа lsx::parser::LexemeType::Empty. Этот тип токена будет возвращаться всегда,
     * если лексер достиг конца переданного документа (или переданного фрагмента документа).
     *
     * \todo Необходимо оптимизировать определение принадлежности символа (char)
     * группам чисел и слов (нужен оптимальный поиск в отсортированных множествах, сейчас это не так)
     */
     
    class Tokenizer
    {
    public:
        struct _NumberMask
        {
            std::u16string      chars;          ///< Маска числа
            LexemeType          type;           ///< Тип лексемы
            TokenQualification  qualification;  ///< Уточняющий квалификатор токена
            number_system_t     system;         ///< Система счисления
            std::vector<int>    refs;           ///< Ссылки на маски-продолжения
            bool                is_starting;    ///< Признак использования данной маски в начальном определении числа
            bool                may_final;      ///< Признак не обязательного продолжения
        };
    private:
        /*!
         * \brief Служебное перечисление для передачи признака в защищённые методы
         */
        enum class NationalCharAffiliation
        {
            Latin,      ///< Идентификатор начат с латинской буквы
            National,   ///< Идентификатор начат с буквы национального алфавита
            Extra       ///< Идентификатор начат со вспомогательного символа
        };

    private:
        Scanner                  _scanner;   ///< Сканер входного потока
        LexicalParameters        _param;     ///< Лексические параметры
        std::vector<_NumberMask> _numbers;   ///< Маски для чисел

    public:
        Tokenizer() = delete; // Без инициализации параметров не имеет смысла

        /*!
         * \brief Конструктор лексического анализатора
         * \param uri_index    Индекс наименования входного потока
         * \param input_stream Ссылка на сходной поток
         * \param parameters   Настройки сканера
         * \param context_no   Номер контекста начала разбора (используется при подсветке синтаксиса)
         */
        Tokenizer(uri_index_t uri_index,
                  InputStream_interface & input_stream,
                  const LexicalParameters & parameters,
                  context_index_t context_no=NO_TOKEN_CONTEXT_INDEX);

        /*!
         * \brief Получение очередного токена из заданного документа (фрагмента) без комментариев
         * \return Токен (см. класс Token)
         */
        Token getToken();

        /*!
         * \brief Получение очередного токена из заданного документа (или фрагмента) вместе с комментариями 
         * \return Токен (см. класс Token)
         */
        Token getAnyToken();

        /*!
         * \brief Возвращает текущее состояние обработки токена
         * \return Текущее состояние обработки токена после последнего вызова shift
         */
        TokenLocation makeTokenLocation() const { return _scanner.makeTokenLocation(); }

    protected:
        /*!
         * \brief Пропуск пробельных символов
         */
        void passBlanks();

        /*!
         * \brief Обработка маркированного токена
         * \return Токен
         */
        Token scanMarkup(context_index_t context);

        /*!
         * \brief Обработка идентификатора (слова)
         * \return Токен
         */
        Token scanWord(NationalCharAffiliation first_char);

        /*!
         * \brief Обработка числа
         *
         *  (вообще-то может быть не только число)
         *
         * \param type          Тип токена
         * \param qualification Квалификатор
         * \param lexeme_str    Строка лексемы
         * \return true, если было разобрано число, иначе - false
         */
        bool scanNumber(LexemeType & type, TokenQualification & qualification, std::u16string & lexeme_str);

        /*!
         * \brief Преобразует строку символов в верхний регистр, используя заданный в параметрах латинский и национальный алфавит
         * \param s   Исходная строка
         * \return Строка, преобразованная в верхний регистр
         */
        std::u16string convertToUpper(std::u16string s) const;

        char16_t convertToUpper(char16_t ch) const;
        char16_t convertLatinToUpper(char16_t ch) const;

        bool isBlank(char16_t ch) const;
    };

}

#endif // simodo_token_Tokenizer
